#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
异常处理代码生成测试

测试各种异常场景的代码生成：
1. raise 语句
2. try-except 结构
3. 异常类型映射
4. 异常消息格式化
"""

from tests.pytests.helpers.codegen_helpers import (
    compile_chunk,
    compile_source,
    assert_code_contains,
)


def test_raise_literal_string():
    """测试 raise 字面量字符串"""

    def func():
        raise ValueError("simple error message")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "simple error message");
        }
        """,
    )


def test_raise_with_variable():
    """测试 raise 带变量消息"""

    def func():
        msg: str = "error message"
        raise ValueError(msg)

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            std::string msg = "error message";
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "{}", (msg));
        }
        """,
    )


def test_raise_fstring():
    """测试 raise 带 f-string"""

    def func():
        value: int = 42
        raise ValueError(f"Error code: {value}")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            int value = 42;
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "Error code: {}", (value));
        }
        """,
    )


def test_raise_fstring_and_expressions():
    """测试 raise 带多参数 f-string 和表达式"""

    def func():
        # 多参数 f-string
        x: int = 10
        y: int = 20
        raise RuntimeError(f"Values: {x} and {y}")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            int x = 10;
            int y = 20;
            throw MC_MAKE_EXCEPTION(mcpy::runtime_error, "Values: {} and {}", (x)(y));
        }
        """,
    )

    # 字符串表达式
    def func2():
        code: int = 100
        raise ValueError("Error: " + str(code))

    block = compile_chunk(func2)
    assert_code_contains(
        block,
        """
        void func2() {
            int code = 100;
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "{}", ("Error: " + mc::to_string(code)));
        }
        """,
    )


def test_raise_placeholders_escape():
    """测试 raise 字面量中的占位符转义"""

    def func():
        raise ValueError("Use {x} and ${y}")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "Use {{x}} and $${{y}}");
        }
        """,
    )


def test_try_except_basic():
    """测试基本的 try-except"""

    def func():
        caught: bool = False
        try:
            raise ValueError("test")
        except ValueError:
            caught = True

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            bool caught = false;
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "test");
            } catch (const mcpy::value_error&) {
                caught = true;
            }
        }
        """,
    )


def test_try_except_with_as():
    """测试 try-except 带 as 子句"""

    def func():
        message: str = ""
        try:
            raise ValueError("test error")
        except ValueError as e:
            message = str(e)

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            std::string message = "";
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "test error");
            } catch (const mcpy::value_error& e) {
                message = e.what();
            }
        }
        """,
    )


def test_try_except_multiple_handlers():
    """测试多个 except 子句"""

    def func():
        error_type: str = ""
        try:
            raise ValueError("test")
        except ValueError:
            error_type = "value"
        except TypeError:
            error_type = "type"
        except RuntimeError:
            error_type = "runtime"

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            std::string error_type = "";
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "test");
            } catch (const mcpy::value_error&) {
                error_type = "value";
            } catch (const mcpy::type_error&) {
                error_type = "type";
            } catch (const mcpy::runtime_error&) {
                error_type = "runtime";
            }
        }
        """,
    )


def test_exception_type_mapping():
    """测试各种异常类型的映射"""

    def test_common_types():
        # ValueError, TypeError, RuntimeError
        try:
            raise ValueError("value")
        except ValueError:
            pass
        try:
            raise TypeError("type")
        except TypeError:
            pass
        try:
            raise RuntimeError("runtime")
        except RuntimeError:
            pass

    block = compile_chunk(test_common_types)
    assert_code_contains(
        block,
        """
        void test_common_types() {
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "value");
            } catch (const mcpy::value_error&) {
            }
            try {
                throw MC_MAKE_EXCEPTION(mcpy::type_error, "type");
            } catch (const mcpy::type_error&) {
            }
            try {
                throw MC_MAKE_EXCEPTION(mcpy::runtime_error, "runtime");
            } catch (const mcpy::runtime_error&) {
            }
        }
        """,
    )

    # IndexError 和 KeyError
    def test_index_and_key():
        try:
            raise IndexError("index out of range")
        except IndexError:
            pass
        try:
            raise KeyError("key not found")
        except KeyError:
            pass

    block = compile_chunk(test_index_and_key)
    assert_code_contains(
        block,
        """
        void test_index_and_key() {
            try {
                throw MC_MAKE_EXCEPTION(mcpy::index_error, "index out of range");
            } catch (const mcpy::index_error&) {
            }
            try {
                throw MC_MAKE_EXCEPTION(mcpy::key_error, "key not found");
            } catch (const mcpy::key_error&) {
            }
        }
        """,
    )


def test_raise_empty_message():
    """测试 raise 空消息（使用默认构造函数，MC_MAKE_EXCEPTION 不支持无参数）"""

    def func():
        raise ValueError()

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            throw mcpy::value_error();
        }
        """,
    )


def test_nested_try_except():
    """测试嵌套 try-except"""

    def func():
        result: str = ""
        try:
            try:
                raise ValueError("inner")
            except ValueError:
                raise RuntimeError("outer")
        except RuntimeError as e:
            result = str(e)

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            std::string result = "";
            try {
                try {
                    throw MC_MAKE_EXCEPTION(mcpy::value_error, "inner");
                } catch (const mcpy::value_error&) {
                    throw MC_MAKE_EXCEPTION(mcpy::runtime_error, "outer");
                }
            } catch (const mcpy::runtime_error& e) {
                result = e.what();
            }
        }
        """,
    )


def test_exception_with_fstring_complex_expr():
    """测试带复杂表达式的 f-string 异常"""

    def func():
        x: int = 10
        y: int = 5
        raise ValueError(f"Result: {x + y * 2}")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            int x = 10;
            int y = 5;
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "Result: {}", (x + y * 2));
        }
        """,
    )


def test_exception_format_injection_safety():
    """测试异常消息防格式化注入"""

    def func():
        user_input: str = "Error ${dangerous}"
        raise ValueError(user_input)

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            std::string user_input = "Error ${dangerous}";
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "{}", (user_input));
        }
        """,
    )


def test_exception_in_conditional():
    """测试条件语句中的异常"""

    def func():
        value: int = 10
        if value > 5:
            raise ValueError("too large")
        else:
            raise ValueError("too small")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            int value = 10;
            if (value > 5) {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "too large");
            } else {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "too small");
            }
        }
        """,
    )


def test_reraise_exception():
    """测试重新抛出异常的多种方式"""

    # 方式1: 直接 raise（不带参数）
    def func():
        try:
            raise ValueError("original")
        except ValueError as e:
            raise

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "original");
            } catch (const mcpy::value_error& e) {
                throw;
            }
        }
        """,
    )

    # 方式2: raise e（显式抛出捕获的异常）
    def func2():
        try:
            raise ValueError("original error")
        except ValueError as e:
            raise e

    block = compile_chunk(func2)
    assert_code_contains(
        block,
        """
        void func2() {
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "original error");
            } catch (const mcpy::value_error& e) {
                throw e;
            }
        }
        """,
    )

    # 方式3: 条件性重新抛出
    def func3():
        should_reraise: bool = True
        try:
            raise ValueError("test")
        except ValueError as e:
            if should_reraise:
                raise e

    block = compile_chunk(func3)
    assert_code_contains(
        block,
        """
        void func3() {
            bool should_reraise = true;
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "test");
            } catch (const mcpy::value_error& e) {
                if (should_reraise) {
                    throw e;
                }
            }
        }
        """,
    )


def test_exception_with_multiple_fstring_args():
    """测试多个参数的 f-string 异常"""

    def func():
        min_val: int = 0
        max_val: int = 100
        actual: int = 150
        raise ValueError(f"Value {actual} not in range [{min_val}, {max_val}]")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            int min_val = 0;
            int max_val = 100;
            int actual = 150;
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "Value {} not in range [{}, {}]", (actual)(min_val)(max_val));
        }
        """,
    )


def test_exception_in_loop():
    """测试循环中的异常"""

    def func():
        for i in range(5):
            if i > 3:
                raise ValueError(f"Value {i} is too large")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            for (int i = 0; i < 5; ++i) {
                if (i > 3) {
                    throw MC_MAKE_EXCEPTION(mcpy::value_error, "Value {} is too large", (i));
                }
            }
        }
        """,
    )


def test_exception_with_return():
    """测试异常处理与返回值的结合"""

    def func() -> int:
        result: int = 0
        try:
            raise ValueError("test")
        except ValueError:
            result = 10
        return result

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        int func() {
            int result = 0;
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "test");
            } catch (const mcpy::value_error&) {
                result = 10;
            }
            return result;
        }
        """,
    )


def test_raise_exception_type_without_args():
    """测试 raise 异常类型（无参数，使用默认构造函数）"""

    def func():
        # 无参数直接抛出异常类型
        raise ValueError

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            throw mcpy::value_error();
        }
        """,
    )


def test_raise_multiple_exception_types():
    """测试多种异常类型的无参数抛出（使用默认构造函数）"""

    def func():
        condition: int = 1
        if condition == 1:
            raise ValueError
        elif condition == 2:
            raise TypeError
        else:
            raise RuntimeError

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            int condition = 1;
            if (condition == 1) {
                throw mcpy::value_error();
            } else if (condition == 2) {
                throw mcpy::type_error();
            } else {
                throw mcpy::runtime_error();
            }
        }
        """,
    )


def test_exception_in_nested_conditions():
    """测试嵌套条件中的异常"""

    def func():
        x: int = 5
        y: int = 10
        if x > 0:
            if y > 5:
                raise ValueError(f"Both positive: x={x}, y={y}")
            else:
                raise TypeError("x positive, y not large")

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            int x = 5;
            int y = 10;
            if (x > 0) {
                if (y > 5) {
                    throw MC_MAKE_EXCEPTION(mcpy::value_error, "Both positive: x={}, y={}", (x)(y));
                } else {
                    throw MC_MAKE_EXCEPTION(mcpy::type_error, "x positive, y not large");
                }
            }
        }
        """,
    )


def test_exception_message_construction():
    """测试异常消息的各种构造方式"""

    # 字符串拼接
    def func():
        prefix: str = "Error:"
        suffix: str = "occurred"
        raise ValueError(prefix + " " + suffix)

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        void func() {
            std::string prefix = "Error:";
            std::string suffix = "occurred";
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "{}", (prefix + " " + suffix));
        }
        """,
    )

    # 条件表达式
    def func2():
        error_code: int = 404
        msg: str = "Not found" if error_code == 404 else "Unknown error"
        raise ValueError(msg)

    block = compile_chunk(func2)
    assert_code_contains(
        block,
        """
        void func2() {
            int error_code = 404;
            std::string msg = (error_code == 404) ? "Not found" : "Unknown error";
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "{}", (msg));
        }
        """,
    )

    # 方法调用
    src_code = """
def get_error_code() -> int:
    return 500

def func3():
    raise ValueError(f"Error code: {get_error_code()}")
"""

    block = compile_chunk(src_code)
    assert_code_contains(
        block,
        """
        void func3() {
            throw MC_MAKE_EXCEPTION(mcpy::value_error, "Error code: {}", (get_error_code()));
        }
        """,
    )


def test_multiple_catches_with_different_actions():
    """测试多个 catch 子句执行不同操作"""

    def func() -> int:
        result: int = 0
        try:
            raise ValueError("test")
        except ValueError:
            result = 1
        except TypeError as e:
            result = 2
            raise e
        except RuntimeError:
            raise NotImplementedError("not implemented")

        return result

    block = compile_chunk(func)
    assert_code_contains(
        block,
        """
        int func() {
            int result = 0;
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "test");
            } catch (const mcpy::value_error&) {
                result = 1;
            } catch (const mcpy::type_error& e) {
                result = 2;
                throw e;
            } catch (const mcpy::runtime_error&) {
                throw MC_MAKE_EXCEPTION(mcpy::not_implemented_error, "not implemented");
            }
            return result;
        }
        """,
    )


def test_exception_constructor_in_various_contexts():
    """测试异常构造函数在各种上下文中的使用

    测试场景：
    1. 函数返回异常对象
    2. 变量存储异常对象
    3. 条件表达式中构造异常
    4. 作为函数参数传递异常对象
    5. 无参数异常构造
    """
    src_code = """
def make_exception(msg: str):
    # 场景1: 函数返回异常对象
    return ValueError(msg)

def make_exception_with_fstring(code: int):
    # 场景1: 函数返回带 f-string 的异常
    return RuntimeError(f"Error code: {code}")

def make_empty_exception():
    # 场景5: 无参数异常构造
    return TypeError()

def handle_error(exc):
    # 场景4: 异常对象作为参数
    raise exc

def func():
    # 场景2: 变量存储异常对象
    err1 = ValueError("stored error")
    
    # 场景2: 存储 f-string 异常
    code: int = 500
    err2 = RuntimeError(f"HTTP {code}")
    
    # 场景3: 条件表达式中构造
    x: int = 10
    err3 = ValueError("too small") if x < 100 else TypeError("too large")
    
    # 场景5: 无参数构造
    err4 = NotImplementedError()
    
    # 使用构造的异常
    raise err1
"""
    _, cpp = compile_source(src_code)

    # 验证完整的生成代码
    assert_code_contains(
        cpp,
        """
        mcpy::value_error make_exception(const std::string& msg) {
            return MC_MAKE_EXCEPTION(mcpy::value_error, "{}", (msg));
        }

        mcpy::runtime_error make_exception_with_fstring(int code) {
            return MC_MAKE_EXCEPTION(mcpy::runtime_error, "Error code: {}", (code));
        }

        mcpy::type_error make_empty_exception() {
            return mcpy::type_error();
        }

        void handle_error(const mc::variant& exc) {
            throw exc;
        }

        void func() {
            mcpy::value_error err1 = MC_MAKE_EXCEPTION(mcpy::value_error, "stored error");
            int code = 500;
            mcpy::runtime_error err2 = MC_MAKE_EXCEPTION(mcpy::runtime_error, "HTTP {}", (code));
            int x = 10;
            mcpy::value_error err3 = (x < 100) ? MC_MAKE_EXCEPTION(mcpy::value_error, "too small") : MC_MAKE_EXCEPTION(mcpy::type_error, "too large");
            mcpy::not_implemented_error err4 = mcpy::not_implemented_error();
            throw err1;
        }
        """,
    )


def test_try_except_else():
    """测试 try/except/else 子句"""

    src_code = """
def test_try_else():
    result1: int = 0
    result2: int = 0
    
    try:
        x: int = 10
    except ValueError:
        result1 = 1
    except TypeError:
        result1 = 2
    else:
        result1 = 3
    
    try:
        raise ValueError("error")
    except ValueError:
        result2 = 4
    except TypeError:
        result2 = 5
    else:
        result2 = 6

def test_another_function():
    result: int = 0
    try:
        y: int = 20
    except ValueError:
        result = 10
    else:
        result = 20
"""

    _, cpp = compile_source(src_code)
    assert_code_contains(
        cpp,
        """
        void test_try_else() {
            int result1 = 0;
            int result2 = 0;
            bool __no_exception = true;
            try {
                int x = 10;
            } catch (const mcpy::value_error&) {
                __no_exception = false;
                result1 = 1;
            } catch (const mcpy::type_error&) {
                __no_exception = false;
                result1 = 2;
            }
            if (__no_exception) {
                result1 = 3;
            }
            bool __no_exception_2 = true;
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "error");
            } catch (const mcpy::value_error&) {
                __no_exception_2 = false;
                result2 = 4;
            } catch (const mcpy::type_error&) {
                __no_exception_2 = false;
                result2 = 5;
            }
            if (__no_exception_2) {
                result2 = 6;
            }
        }
        void test_another_function() {
            int result = 0;
            bool __no_exception = true;
            try {
                int y = 20;
            } catch (const mcpy::value_error&) {
                __no_exception = false;
                result = 10;
            }
            if (__no_exception) {
                result = 20;
            }
        }
        """,
    )


def test_try_finally_basic():
    """测试基本的 try/finally 子句

    使用 goto 方案实现 Python finally 语义：
    - finally 代码只生成一次（放在 finally 标签处）
    - 所有退出点（正常流程/异常/return/break/continue）都跳转到 finally
    - 零运行时开销，完整语义支持
    """

    # 场景1: try/except/finally
    def func1():
        result: int = 0
        try:
            result = 1
        except ValueError:
            result = 2
        finally:
            result = 3

    block = compile_chunk(func1)
    assert_code_contains(
        block,
        """
        void func1() {
            int result = 0;
            std::exception_ptr __exception_ptr = nullptr;
            bool __has_return = false;
            try {
                result = 1;
            } catch (const mcpy::value_error&) {
                result = 2;
            } catch (...) {
                __exception_ptr = std::current_exception();
                goto __finally;
            }
            __finally : {
                result = 3;
            }
            if (__exception_ptr) {
                std::rethrow_exception(__exception_ptr);
            }
            if (__has_return) {
                return;
            }
        }
        """,
    )

    # 场景2: try/finally（没有 except）
    def func2():
        result: int = 0
        try:
            result = 1
        finally:
            result = 2

    block = compile_chunk(func2)
    assert_code_contains(
        block,
        """
        void func2() {
            int result = 0;
            std::exception_ptr __exception_ptr = nullptr;
            bool __has_return = false;
            try {
                result = 1;
            } catch (...) {
                __exception_ptr = std::current_exception();
                goto __finally;
            }
            __finally : {
                result = 2;
            }
            if (__exception_ptr) {
                std::rethrow_exception(__exception_ptr);
            }
            if (__has_return) {
                return;
            }
        }
        """,
    )

    # 场景3: try/except/else/finally
    def func3():
        result: int = 0
        try:
            x: int = 10
        except ValueError:
            result = 1
        else:
            result = 2
        finally:
            result = 3

    block = compile_chunk(func3)
    assert_code_contains(
        block,
        """
        void func3() {
            int result = 0;
            std::exception_ptr __exception_ptr = nullptr;
            bool __has_return = false;
            bool __no_exception = true;
            try {
                int x = 10;
            } catch (const mcpy::value_error&) {
                __no_exception = false;
                result = 1;
            } catch (...) {
                __exception_ptr = std::current_exception();
                goto __finally;
            }
            if (__no_exception) {
                result = 2;
            }
            __finally : {
                result = 3;
            }
            if (__exception_ptr) {
                std::rethrow_exception(__exception_ptr);
            }
            if (__has_return) {
                return;
            }
        }
        """,
    )


def test_try_finally_with_early_exit():
    """测试 finally 在提前退出（return/throw）情况下的行为

    验证使用 RAII scope_guard 实现的真正 finally 语义：
    - try 中 return，finally 仍会执行
    - catch 中 return，finally 仍会执行
    - catch 中 throw，finally 仍会执行
    """

    src_code = """
def func_try_return() -> int:
    # try 中 return，finally 应该执行
    result: int = 0
    try:
        return 10
    finally:
        result = 99
    return result

def func_catch_return() -> int:
    # catch 中 return，finally 应该执行
    result: int = 0
    try:
        raise ValueError("error")
    except ValueError:
        return 20
    finally:
        result = 99
    return result

def func_catch_throw():
    # catch 中 throw，finally 应该执行
    try:
        raise ValueError("inner")
    except ValueError:
        raise RuntimeError("outer")
    finally:
        pass
"""

    _, cpp = compile_source(src_code)
    assert_code_contains(
        cpp,
        """
        int func_try_return() {
            int result = 0;
            std::exception_ptr __exception_ptr = nullptr;
            bool __has_return = false;
            int __return_value{};
            try {
                __has_return = true;
                __return_value = 10;
                goto __finally;
            } catch (...) {
                __exception_ptr = std::current_exception();
                goto __finally;
            }
            
            __finally : {
                result = 99;
            }
            if (__exception_ptr) {
                std::rethrow_exception(__exception_ptr);
            }
            if (__has_return) {
                return __return_value;
            }
            return result;
        }
        
        int func_catch_return() {
            int result = 0;
            std::exception_ptr __exception_ptr = nullptr;
            bool __has_return = false;
            int __return_value{};
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "error");
            } catch (const mcpy::value_error&) {
                __has_return = true;
                __return_value = 20;
                goto __finally;
            } catch (...) {
                __exception_ptr = std::current_exception();
                goto __finally;
            }
            
            __finally : {
                result = 99;
            }
            if (__exception_ptr) {
                std::rethrow_exception(__exception_ptr);
            }
            if (__has_return) {
                return __return_value;
            }
            return result;
        }
        
        void func_catch_throw() {
            std::exception_ptr __exception_ptr = nullptr;
            bool __has_return = false;
            try {
                throw MC_MAKE_EXCEPTION(mcpy::value_error, "inner");
            } catch (const mcpy::value_error&) {
                __exception_ptr = std::make_exception_ptr(MC_MAKE_EXCEPTION(mcpy::runtime_error, "outer"));
                goto __finally;
            } catch (...) {
                __exception_ptr = std::current_exception();
                goto __finally;
            }
            
            __finally : {
            }
            if (__exception_ptr) {
                std::rethrow_exception(__exception_ptr);
            }
            if (__has_return) {
                return;
            }
        }
        """,
    )
